import { expectType } from 'tsd'

// ! ------递归属性修饰
type PromiseValue<T> = T extends Promise<infer V> ? PromiseValue<V> : T

type test1 = PromiseValue<PromiseValue<true>>

//此时我们只是在条件类型成立时，再次调用了这个工具类型而已。在某一次递归到条件类型不成立时，就会直接返回这个类型值。那么对于 Partial、Required，其实我们也可以进行这样地处理：
// # DeepPartial
export type DeepPartial<T extends object> = {
  [K in keyof T]+?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
//简单起见，我们直接使用了 object 作为泛型约束与条件，这意味着也有可能传入函数、数组等类型。但毕竟我们对这个类型知根知底，就可以假设只会传入对象结构，因此也只需要对对象类型进行处理了。
type test2 = {
  name: {
    age: number
  }
}

type test3 = DeepPartial<test2>

const obj1: test3 = {
  name: {
    age: 2
  }
}

//为了更直观地验证它的效果，我们使用 tsd 这一工具类型单元测试库来进行验证，效果大概是这样：
type DeepPartialStruct = DeepPartial<{
  foo: string
  nested: {
    nestedFoo: string
    nestedBar: {
      nestedBarFoo: string
    }
  }
}>

expectType<DeepPartialStruct>({
  foo: 'bar',
  nested: {}
})
expectType<DeepPartialStruct>({
  nested: {
    nestedBar: {}
  }
})

expectType<DeepPartialStruct>({
  nested: {
    nestedBar: {
      nestedBarFoo: undefined
    }
  }
})
// # DeepRequired
//在 expectType 的泛型坑位中传入一个类型，然后再传入一个值，就可以验证这个值是否符合泛型类型了。
export type DeepRequired<T extends object> = {
  [K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K]
}

type DeepRequiredStruct = DeepRequired<{
  foo: string
  nested: {
    nestedFoo: string
    nestedBar: {
      nestedBarFoo: string
    }
  }
}>

expectType<DeepRequiredStruct>({
  foo: 'string',
  nested: {
    nestedFoo: 'string',
    nestedBar: {
      nestedBarFoo: 'string'
    }
  }
})

type test5 = DeepRequired<{ name: { age?: number } }>

// 也可以记作 DeepImmutable
// # DeepReadonly
export type DeepReadonly<T extends object> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
// # DeepMutable
export type DeepMutable<T extends object> = {
  -readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]> : T[K]
}

type DeepReadonlyStruct = DeepReadonly<{
  readonly foo: string
  nested: {
    nestedFoo: string
    nestedBar: {
      nestedBarFoo: string
    }
  }
}>

expectType<DeepReadonlyStruct>({
  foo: 'string',
  nested: {
    nestedFoo: 'string',
    nestedBar: {
      nestedBarFoo: 'string'
    }
  }
})

//另外，在内置工具类型一节的结构工具类型中，存在一个从联合类型中剔除 null | undefined 的工具类型 NonNullable：
type NonNullable<T> = T extends null | undefined ? never : T

// # DeepNonNullable
export type DeepNonNullable<T extends object> = {
  [K in keyof T]: T[K] extends object ? DeepNonNullable<T[K]> : NonNullable<T[K]>
}

type test6 = DeepNonNullable<{ name: null; limin: { name: 'lys'; age: undefined } }>

// # DeepNullable
export type Nullable<T> = T | null

export type DeepNullable<T extends object> = {
  [K in keyof T]: T[K] extends object ? DeepNullable<T[K]> : Nullable<T[K]>
}

type test7 = DeepNullable<{ name: null; limin: { name: 'lys'; age: undefined } }>

const test7_0: test7 = {
  name: null,
  limin: { name: 'lys', age: undefined }
}

// !----------- 基于已知属性进行部分修饰了

export type MarkPropsAsOptional<T extends object, K extends keyof T = keyof T> = Partial<Pick<T, K>> & Omit<T, K>
//T 为需要处理的对象类型，而 K 为需要标记为可选的属性。由于此时 K 必须为 T 内部的属性，因此我们将其约束为 keyof T，即对象属性组成的字面量联合类型。同时为了让它能够直接代替掉 Partial，我们为其指定默认值也为 keyof T，这样在不传入第二个泛型参数时，它的表现就和 Partial 一致，即全量的属性可选。而其组成中，Partial<Pick<T, K>> 为需要标记为可选的属性组成的对象子结构，Omit<T, K> 则为不需要处理的部分，使用交叉类型将其组合即可。我们验证下效果：
type MarkPropsAsOptionalStruct = MarkPropsAsOptional<
  {
    foo: string
    bar: number
    baz: boolean
  },
  'bar'
>
// 不好看出来具体效果。此时我们可以引入一个辅助的工具类型，我称其为 Flatten，对于这种交叉类型的结构，Flatten 能够将它展平为单层的对象结构
export type Flatten<T> = { [K in keyof T]: T[K] }

// 升级
export type MarkPropsAsOptional1<T extends object, K extends keyof T = keyof T> = Flatten<Partial<Pick<T, K>> & Omit<T, K>>

type MarkPropsAsOptionalStruct1 = MarkPropsAsOptional1<
  {
    foo: string
    bar: number
    baz: boolean
  },
  'bar'
>

//我们来实现其它类型的部分修饰：
export type MarkPropsAsRequired<T extends object, K extends keyof T = keyof T> = Flatten<Required<Pick<T, K>> & Omit<T, K>>

type MarkPropsAsRequiredStruct = MarkPropsAsRequired<
  {
    foo: string
    bar: number
    baz: boolean
  },
  'bar'
>

export type MarkPropsAsReadonly<T extends object, K extends keyof T = keyof T> = Flatten<Readonly<Pick<T, K>> & Omit<T, K>>

type MarkPropsAsReadonlyStruct = MarkPropsAsReadonly<
  {
    foo: string
    bar: number
    baz: boolean
  },
  'bar' | 'baz'
>

type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}

export type MarkPropsMutable<T extends object, K extends keyof T = keyof T> = Flatten<Mutable<Pick<T, K>> & Omit<T, K>>

type MarkPropsMutableStruct = MarkPropsMutable<
  {
    readonly foo: string
    readonly bar: number
    readonly baz: boolean
  },
  'bar'
>

export type MarkPropsAsNullable<T extends object, K extends keyof T = keyof T> = Flatten<Nullable<Pick<T, K>> & Omit<T, K>>

type MarkPropsAsNullableStruct = MarkPropsAsNullable<
  {
    foo: string
    bar: null
    baz: boolean
  },
  'bar'
>

// type NonNullable<T> = T extends null | undefined ? never : T
type NonNullableOfObj<T extends object> = {
  [K in keyof T]: T[K] extends object ? NonNullableOfObj<T[K]> : NonNullable<T[K]>
}
export type MarkPropsAsNonNullable<T extends object, K extends keyof T = keyof T> = Flatten<NonNullableOfObj<Pick<T, K>> & Omit<T, K>>

type MarkPropsAsNonNullableStruct = MarkPropsAsNonNullable<
  {
    foo: string
    bar: null
    baz: boolean
  },
  'bar'
>
